package com.quiz.file.controller;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Dict;
import com.quiz.common.core.domain.AjaxResult;
import com.quiz.common.core.redis.RedisCache;
import com.quiz.common.exception.ServiceException;
import com.quiz.common.utils.DateUtils;
import com.quiz.common.utils.StringUtils;
import com.quiz.common.utils.file.FileUploadUtils;
import com.quiz.common.utils.file.FileUtils;
import com.quiz.file.constant.FileConstant;
import com.quiz.file.domain.Bo.FileInfoBo;
import com.quiz.file.domain.model.FileInfo;
import com.quiz.file.domain.vo.FileInfoVo;
import com.quiz.file.service.IFileService;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.quiz.common.config.QuizConfig;
import com.quiz.common.core.domain.R;
import com.quiz.file.service.IFileTokenService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jodconverter.DocumentConverter;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;


/**
 * 文件管理
 *
 * @author ahuan
 */
@Slf4j
@RestController
@RequestMapping("/file")
@Tag(name = "文件管理")
@RequiredArgsConstructor
public class FileController {

    private final IFileService baseService;

    private final RedisCache redis;

    private final IFileTokenService tokenService;


    /**
     * 文件上传接口
     *
     * @param file 上传的文件
     * @return 上传结果
     */
    @PostMapping("/upload")
    public R<List<FileInfoVo>> upload(@RequestParam MultipartFile file) {
        // 校验
        if (file.isEmpty()) {
            return R.fail("上传文件不能为空");
        }
        try {
            // 上传文件并且返回数据模型
            FileInfoBo bo = generateFileInfo(file);
            log.info("=========>文件上传开始，文件名：{}", bo.toString());
            // 记录文件信息到数据库
            baseService.sava(bo);
            // 生成文件视图对象
            FileInfoVo vo = FileInfoVo.builder()
                    .fileType(bo.getFileType())
                    .fileOriginName(bo.getFileOriginName())
                    .fileUrl(QuizConfig.getBaseUrl() + bo.getFileUrl())
                    .fileId(bo.getFileId().toString())
                    .build();
            log.info("=========>文件上传成功，文件名：{}", vo.toString());
            return R.ok(Arrays.asList(vo));
        } catch (IOException e) {
            log.error("=========>文件上传失败", e);
            return R.fail("文件上传失败");
        }
    }


    /**
     * 文件上传接口（批量上传）
     *
     * @param files 上传的文件数组
     * @return 上传结果
     */
    @PostMapping("/upload/batch")
    public R<List<FileInfoVo>> uploadBatch(@RequestParam("files") MultipartFile[] files) {
        if (files == null || files.length == 0) {
            return R.fail("上传文件不能为空");
        }

        try {
            // 创建上传目录（如果不存在）
            // 上传文件路径

            List<FileInfoBo> bos = new ArrayList<>();
            List<FileInfoVo> vos = new ArrayList<>();
            for (MultipartFile file : files) {
                if (file.isEmpty()) {
                    continue; // 跳过空文件
                }

                // 根据文件信息生成数据模型
                FileInfoBo bo = generateFileInfo(file);
                // 记录文件信息到列表
                bos.add(bo);
                // 生成文件视图对象
                FileInfoVo vo = BeanUtil.copyProperties(bo, FileInfoVo.class);
                vo.setFileUrl(QuizConfig.getBaseUrl() + bo.getFileUrl());
                log.info("=========>文件上传成功，文件名：{}", vo.toString());
                vos.add(vo);
            }
            // 持久化到数据库
            baseService.savaBatch(bos);
            return R.ok(vos);
        } catch (IOException e) {
            log.error("=========>文件上传失败", e);
            return R.fail("文件上传失败");
        }
    }


    /**
     * 公共文件上传接口
     *
     * @param file 上传的文件
     * @return 上传结果
     */
    @PostMapping("/upload/public")
    public R<List<FileInfoVo>> uploadPublic(@RequestParam MultipartFile file) {
        // 校验
        if (file.isEmpty()) {
            return R.fail("上传文件不能为空");
        }
        try {
            // 上传文件并且返回数据模型
            FileInfoBo bo = generateFileInfo(file);
            log.info("=========>文件上传开始，文件名：{}", bo.toString());
            // 记录文件信息到数据库
            baseService.sava(bo);
            // 生成文件视图对象
            FileInfoVo vo = FileInfoVo.builder()
                    .fileType(bo.getFileType())
                    .fileOriginName(bo.getFileOriginName())
                    .fileUrl(QuizConfig.getBaseUrl() + bo.getFileUrl())
                    .fileId(bo.getFileId().toString())
                    .build();
            log.info("=========>文件上传成功，文件名：{}", vo.toString());
            return R.ok(Arrays.asList(vo));
        } catch (IOException e) {
            log.error("=========>文件上传失败", e);
            return R.fail("文件上传失败");
        }
    }

    /**
     * 文件下载接口
     *
     * @param id       要下载的文件ID
     * @param response HttpServletResponse对象
     */
    @GetMapping("/download/{id}")
    public void download(@PathVariable("id") Long id, HttpServletResponse response) throws UnsupportedEncodingException {
        FileInfo fileInfo = baseService.getFileById(id);

        File file = new File(fileInfo.getFilePath());

        if (!file.exists()) {
            throw new ServiceException("文件不存在");
        }

        response.setContentType("application/octet-stream");
        FileUtils.setAttachmentResponseHeader(response, fileInfo.getFileOriginName());
        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            log.info("文件不存在", e);
            throw new RuntimeException("文件不存在");

        }
    }

    /**
     * 获取临时访问token
     *
     * @param ids      要下载的文件IDs
     * @param response HttpServletResponse对象
     */
    @PostMapping("/ttk/token")
    public R<List<FileInfoVo>> getTtkToken(@RequestBody String ids, HttpServletResponse response) throws UnsupportedEncodingException {
        if(StringUtils.isEmpty(ids)){
            return R.fail("请选择要下载的文件");
        }
        //如果开头和结尾有""，去掉
        ids = ids.replaceAll("^\"|\"$", "");
        //去掉开头和结尾的双引号
        String token = tokenService.createToken(ids);
        redis.setCacheObject(FileConstant.FILE_TOKEN_PREFIX + token, token, 30, TimeUnit.MINUTES);
        //生成临时访问链接
        long[] array = Arrays.stream(ids.split(",")).mapToLong(Long::parseLong).toArray();
        List<FileInfo> files = baseService.getFileByIds(array);
        for (FileInfo file : files) {
            file.setFileUrl(QuizConfig.getBaseUrl() + "/file/download/dynamic/linking/" +file.getFileId()+ "?token=" + token);
        }
        List<FileInfoVo> filesVo = BeanUtil.copyToList(files, FileInfoVo.class);
        return R.ok(filesVo);
    }


    /**
     * 文件下载接口-动态链接
     * <p>
     * 需要登录认证后获取临时访问token
     *
     * @param token    要下载的文件ID
     * @param response HttpServletResponse对象
     */
    @GetMapping("/download/dynamic/linking/{fileId}")
    public void downloadDynamic(@RequestParam("token") String token,
                                @PathVariable("fileId") String fileId,
                                HttpServletResponse response) throws UnsupportedEncodingException {

        if (!redis.hasKey(FileConstant.FILE_TOKEN_PREFIX + token)) {
            throw new ServiceException("链接访问无效，请认证后获取系统资源!!!");
        }
        FileInfo fileInfo = tokenService.getFileInfo(token,fileId);

        File file = new File(fileInfo.getFilePath());

        if (!file.exists()) {
            throw new ServiceException("文件不存在");
        }

        response.setContentType("application/octet-stream");
        FileUtils.setAttachmentResponseHeader(response, fileInfo.getFileOriginName());
        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            log.info("文件不存在", e);
            throw new RuntimeException("文件不存在");
        } finally {
            redis.deleteObject(FileConstant.FILE_TOKEN_PREFIX + token);
        }
    }

    /**
     * 共享文件预览
     *
     * @param id 要删除的文件ID
     * @return 删除结果
     */
    @GetMapping("/preview/public")
    public R<FileInfo> previewPublic(@RequestParam("id") Long id) {
        FileInfo fileInfo = baseService.getFileById(id);
        if (isOfficeFile(fileInfo.getFileType())) {
            String token = tokenService.createToken(id);
            redis.setCacheObject(FileConstant.FILE_TOKEN_PREFIX + token, fileInfo, 60, TimeUnit.SECONDS);
            String newFileUrl = QuizConfig.getBaseUrl() + "/file/download/dynamic/linking?token=" + token;
            fileInfo.setFileUrl("https://view.officeapps.live.com/op/embed.aspx?src=" + newFileUrl);
        }
        log.info("=========>文件预览信息获取成功，文件地址：{}", fileInfo.getFileUrl());
        return R.ok(fileInfo);
    }


    /**
     * 文件删除接口
     *
     * @param id 要删除的文件ID
     * @return 删除结果
     */
    @DeleteMapping("/delete/{id}")
    public R<Void> deleteFile(@PathVariable("id") Long id) {
        // 1. 查询文件信息
        FileInfo fileInfo = baseService.getFileById(id);
        if (fileInfo == null) {
            return R.fail("文件不存在");
        }

        try {
            // 2. 删除数据库记录
            boolean dbResult = baseService.removeById(id);
            if (!dbResult) {
                return R.fail("数据库删除失败");
            }

            // 3. 删除物理文件
            File file = new File(fileInfo.getFilePath());
            if (file.exists()) {
                if (!file.delete()) {
                    log.error("文件删除失败，ID：{}，路径：{}", id, fileInfo.getFilePath());
                    return R.fail("物理文件删除失败");
                }
            }

            return R.ok();
        } catch (Exception e) {
            log.error("文件删除异常，ID：{}", id, e);
            return R.fail("删除过程中发生异常");
        }
    }

    /**
     * 获取转换为pdf后端的office在线预览
     */
    @GetMapping("/preview/pdf/{id}")
    public void onlineOfficeToPdfAndOnlinePreview(@PathVariable("id") Long id, HttpServletResponse response) throws Exception {

        baseService.onlinePreview(id, response);

    }

    /**
     * 获取文件列表详情
     */
    @PostMapping("/detail")
    public R<List<FileInfoVo>> details(@RequestBody String fileIds) {
        return R.ok(baseService.details(fileIds));
    }


    /**
     * 生成文件信息
     * <p>
     * 雪花算法：
     * <p>
     * 雪花算法的原理就是生成一个的 64 位比特位的 long 类型的唯一 id。
     * <p>
     * 1：最高 1 位是符号位，固定值 0，表示id 是正整数
     * 41：接下来 41 位存储毫秒级时间戳，2^41/(1000606024365)=69，大概可以使用 69 年。
     * 10：再接下 10 位存储机器码，包括 5 位 datacenterId 和 5 位 workerId。最多可以部署 2^10=1024 台机器。
     * 12：最后 12 位存储序列号。同一毫秒时间戳时，通过这个递增的序列号来区分。即对于同一台机器而言，同一毫秒时间戳下，可以生成 2^12=4096 个不重复 id。
     *
     * @param file
     * @return
     */
    private FileInfoBo generateFileInfo(MultipartFile file) throws IOException {
        // 上传basePath
        String uploadPath = QuizConfig.getUploadPath();

        // 生成文件数据模型
        FileInfoBo bo = new FileInfoBo();
        String fileOriginalName = file.getOriginalFilename();
        Long fileId = IdWorker.getId();
        log.info("=========>FILE_ID：{}", fileId);
        String fileName = FileUploadUtils.extractFilename2(file);
        String filePath = uploadPath + "/" + DateUtils.datePath() + "/" + fileName;
        String fileUrl = "/file/download/" + fileId;
        String fileType = fileOriginalName.substring(fileOriginalName.lastIndexOf(".") + 1);
        Long fileSize = file.getSize();

        // 计算文件MD5
        String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());

        // 上传文件
        FileUploadUtils.uploadToPath(filePath, file);

        // 返回数据模型
        return FileInfoBo.builder()
                .fileId(fileId)
                .fileName(fileName)
                .fileOriginName(fileOriginalName)
                .fileUrl(fileUrl)
                .filePath(filePath)
                .fileType(fileType)
                .fileSize(fileSize)
                .md5(md5)
                .build();
    }


    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf(".");
        return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
    }

    /**
     * 判断是否为图片文件
     */
    private boolean isImageFile(String extension) {
        return extension.equals("jpg") || extension.equals("jpeg") || extension.equals("png") || extension.equals("gif");
    }

    /**
     * 判断是否为 Office 文件（Word, Excel, PPT）
     */
    private boolean isOfficeFile(String extension) {
        return extension.equals("doc") || extension.equals("docx") ||
                extension.equals("xls") || extension.equals("xlsx") ||
                extension.equals("ppt") || extension.equals("pptx");
    }

    /**
     * 判断是否为 PDF 文件
     */
    private boolean isPdfFile(String extension) {
        return extension.equals("pdf");
    }

//    ------------------------------富文本编辑器文件服务---------------------------------------------

    /**
     * 富文本文件上传接口
     *
     * @param file 上传的文件
     * @return 上传结果
     */
    @PostMapping("/upload/editor")
    public AjaxResult uploadEditor(@RequestParam MultipartFile file, @RequestParam String type) {
        // 校验
        if (file.isEmpty()) {
            return AjaxResult.error("上传失败，请检查文件是否为空");
        }
        try {
            // 上传文件并且返回数据模型
            FileInfoBo bo = generateFileInfo(file);
            log.info("=========>文件上传开始，文件信息：{}", bo.toString());
            // 记录文件信息到数据库
            baseService.sava(bo);
            // 生成富文本文件视图对象
            if ("img".equals(type)) {
                return AjaxResult.success(CollUtil.newArrayList(Dict.create()
                        .set("url", bo.getFileUrl())));
            } else {
                return AjaxResult.success(Dict.create()
                        .set("url", bo.getFileUrl()));
            }
        } catch (
                IOException e) {
            log.error("=========>文件上传失败", e);
            throw new ServiceException("富文本文件上传异常," + e.getMessage());
        }
    }

    /**
     * 富文本图片上传接口
     *
     * @param files 上传的文件
     * @return 上传结果
     */
    @PostMapping("/upload/editor/imgs")
    public R<List<FileInfoVo>> uploadEditor(@RequestParam MultipartFile[] files) {
        if (files == null || files.length == 0) {
            return R.fail("上传文件不能为空");
        }

        try {
            // 创建上传目录（如果不存在）
            // 上传文件路径

            List<FileInfoBo> bos = new ArrayList<>();
            List<FileInfoVo> vos = new ArrayList<>();
            for (MultipartFile file : files) {
                if (file.isEmpty()) {
                    continue; // 跳过空文件
                }

                // 根据文件信息生成数据模型
                FileInfoBo bo = generateFileInfo(file);
                // 记录文件信息到列表
                bos.add(bo);
                // 生成文件视图对象
                FileInfoVo vo = BeanUtil.copyProperties(bo, FileInfoVo.class);
                // 填写相对地址，让前端可以根据动态IP获取图片
                vo.setFileUrl(bo.getFileUrl());
                log.info("=========>文件上传成功，文件名：{}", vo.toString());
                vos.add(vo);
            }
            // 持久化到数据库
            baseService.savaBatch(bos);
            return R.ok(vos);
        } catch (IOException e) {
            log.error("=========>文件上传失败", e);
            return R.fail("文件上传失败");
        }
    }


}
